#include <linux/module.h>
#include <linux/kernel.h>//printk()#include <linux/ctype.h>#include <linux/config.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/fcntl.h>
#include <asm/system.h>
#include <asm/uaccess.h>//copy to/from user#include <asm/semaphore.h>//#define __KERNEL__
#include "hw4.h"
#include "encryptor.h"

#define EOF 0 //TODO check if -1 or 0#define MY_MODULE "hw4"#define BUFFER_SIZE 4096

int my_open(struct inode *inode, struct file *filp);
int my_release(struct inode *inode, struct file *filp);
ssize_t my_read(struct file *filp, char *buf, size_t count, loff_t *offp);
ssize_t my_write(struct file *filp, const char *buf, size_t count, loff_t *offp);
loff_t my_llseek(struct file *, loff_t, int);
int my_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
		unsigned long arg);

struct file_operations my_fops = {
		.open = my_open,
		.release = my_release,
		.read = my_read,
		.write = my_write,
		.ioctl = my_ioctl,
		.llseek =my_llseek 		};

MODULE_LICENSE("GPL");

char buffers[2][BUFFER_SIZE];

int read_position[2];
int write_position[2];

int num_readers[2];
int num_writers[2];

int isEmpty[2];
int isFull[2];

wait_queue_head_t read_queue[2];
wait_queue_head_t write_queue[2];

struct semaphore sema_read[2], sema_write[2], sema_count[2];
typedef struct privateData{
	int minor;
	int PIKey;
}PrivateData;

int iKey = 0;
MODULE_PARM(iKey,"i");
int my_major;


//helper functions:

int handle_read_decrypt(int size_to_read, int minor, char* buf,struct file *filp) {
	int result;
	char* tmp_buffer = kmalloc(sizeof(char) * size_to_read, GFP_KERNEL);
	int pr_iKey= ((PrivateData*)(filp->private_data))->PIKey;
	if (!tmp_buffer)
		return -1; // memory failure

	if ((read_position[minor] + size_to_read) < BUFFER_SIZE) { //only one section
		encryptor(&(buffers[minor][read_position[minor]]), tmp_buffer,
				size_to_read, pr_iKey, minor);
		read_position[minor] = (read_position[minor] + size_to_read)
				% BUFFER_SIZE;
	} else {		//need to read in two parts
		int read1 = BUFFER_SIZE - read_position[minor];
		encryptor(&(buffers[minor][read_position[minor]]), tmp_buffer, read1,
				pr_iKey, minor);
		encryptor(&(buffers[minor][0]), tmp_buffer + read1,
				size_to_read - read1, pr_iKey, minor);
		read_position[minor] = size_to_read - read1;
	}
	result = copy_to_user(buf, tmp_buffer, size_to_read);
	kfree(tmp_buffer);
	if ((read_position[minor] == write_position[minor]) && (size_to_read)) {
		isEmpty[minor] = 1;
	}
	if (size_to_read) {
		isFull[minor] = 0;
	}
	if (result) {//number of bytes not copied. 0 means success copying to user
		return result;
	}
	return 0;

}

int handle_read_encrypt(int size_to_read, int minor, char* buf) {
	int result;
	char* tmp_buffer = kmalloc(sizeof(char) * size_to_read, GFP_KERNEL);
	if (!tmp_buffer)
		return -1; // memory failure

	if ((read_position[minor] + size_to_read) < BUFFER_SIZE) { //only one section
		result = copy_to_user(buf, &buffers[minor][read_position[minor]],
				size_to_read);
		if (result == 0) {
			read_position[minor] = (read_position[minor] + size_to_read)
					% BUFFER_SIZE;

		}
	} else {		//need to read in two parts
		int read1 = BUFFER_SIZE - read_position[minor];
		memcpy(tmp_buffer, &buffers[minor][read_position[minor]], read1);
		memcpy(tmp_buffer + read1, &buffers[minor][0], size_to_read - read1);
		result = copy_to_user(buf, tmp_buffer, size_to_read);
		if (result == 0) {
			read_position[minor] = size_to_read - read1;
		}
	}
	kfree(tmp_buffer);
	if ((read_position[minor] == write_position[minor]) && (size_to_read)) {
		isEmpty[minor] = 1;
	}
	if (size_to_read) {
		isFull[minor] = 0;
	}
	if (result) {//number of bytes not copied. 0 means success copying to user
		return result;
	}
	return 0;

}

int write_permission(struct file* filp) {
	if ((filp->f_mode) & FMODE_WRITE)
		return 1;
	return 0;
}

int read_permission(struct file* filp) {
	if ((filp->f_mode) & FMODE_READ)
		return 1;
	return 0;
}

int get_num_writers(int minor) {
	int res;
	down_interruptible(&sema_count[minor]);
	res = num_writers[minor];
	up(&sema_count[minor]);
	return res;
}

int get_num_readers(int minor) {
	int res;
	down_interruptible(&sema_count[minor]);
	res = num_readers[minor];
	up(&sema_count[minor]);
	return res;
}

int get_size_to_read(int minor) {
	int write_pos, read_pos, tmp;
	write_pos = write_position[minor];
	read_pos = read_position[minor];
	if (isFull[minor]) {
		return BUFFER_SIZE;
	} else {
		tmp = write_pos - read_pos;
	}
	return (tmp >= 0 ? tmp : BUFFER_SIZE + tmp);
}

int get_size_to_write(int minor) {
	int write_pos, read_pos, tmp;
	write_pos = write_position[minor];
	read_pos = read_position[minor];
	if (isFull[minor]) {
		tmp = 0;
	} else {
		tmp = read_pos - write_pos;
		tmp = (tmp > 0) ? tmp : (BUFFER_SIZE + tmp);
	}
	return tmp;

}

int handle_write_encrypt(int size_to_write, int minor, const char* buf,struct file *filp) {
	int result;
	int pr_iKey= ((PrivateData*)(filp->private_data))->PIKey;
	char* tmp_buffer = kmalloc(sizeof(char) * size_to_write, GFP_KERNEL);
	if (!tmp_buffer)
		return -1; // memory failure
	result = copy_from_user(tmp_buffer, buf, size_to_write);
	if (result) {
		kfree(tmp_buffer);
		return -1; //failure
	}

	if ((write_position[minor] + size_to_write) < BUFFER_SIZE) { //only one section
		encryptor(tmp_buffer, &buffers[minor][write_position[minor]],
				size_to_write, pr_iKey, minor);
		write_position[minor] = (write_position[minor] + size_to_write)
				% BUFFER_SIZE;
	} else {
		int write1 = BUFFER_SIZE - write_position[minor];
		encryptor(tmp_buffer, &buffers[minor][write_position[minor]], write1,
				pr_iKey, minor);
		encryptor(tmp_buffer + write1, &buffers[minor][0],
				size_to_write - write1, pr_iKey, minor);
		write_position[minor] = (size_to_write - write1);
	}
	kfree(tmp_buffer);
	if ((read_position[minor] == write_position[minor]) && (size_to_write)) {
		isFull[minor] = 1;
	}
	if (size_to_write) {
		isEmpty[minor] = 0;
	}
	if (result) { //number of bytes not copied. 0 means success copying to user
		return result;
	}
	return 0;

}

int handle_write_decrypt(int size_to_write, int minor, const char* buf) {
	int result;
	char* tmp_buffer = kmalloc(sizeof(char) * size_to_write, GFP_KERNEL);
	if (!tmp_buffer)
		return -1; // memory failure
	result = copy_from_user(tmp_buffer, buf, size_to_write);
	if (result) {
		kfree(tmp_buffer);
		return -1; //failure
	}

	if ((write_position[minor] + size_to_write) <= BUFFER_SIZE) { //only one section
		memcpy(&buffers[minor][write_position[minor]], tmp_buffer,
				size_to_write);
		write_position[minor] = (write_position[minor] + size_to_write)
				% BUFFER_SIZE;
	} else {
		int write1 = BUFFER_SIZE - write_position[minor];
		memcpy(&buffers[minor][write_position[minor]], tmp_buffer, write1);
		memcpy(&buffers[minor][0], tmp_buffer + write1, size_to_write - write1);
		write_position[minor] = (size_to_write - write1);
	}
	kfree(tmp_buffer);
	if ((read_position[minor] == write_position[minor]) && (size_to_write)) {
		isFull[minor] = 1;
	}
	if (size_to_write) {
		isEmpty[minor] = 0;
	}

	if (result) { //number of bytes not copied. 0 means success copying to user
		return result;
	}
	return 0;

}



// device functions:

int init_module() {
	//register our module "hw4". passing 0 to dynamically allocate a major number:
	my_major = register_chrdev(0, MY_MODULE, &my_fops);
	if (my_major < 0) {
		return -EFAULT;
	}
	memset(buffers[0], 0, BUFFER_SIZE);
	memset(buffers[1], 0, BUFFER_SIZE);

	read_position[0] = 0;
	read_position[1] = 0;

	write_position[0] = 0;
	write_position[1] = 0;

	num_readers[0] = 0;
	num_readers[1] = 0;

	num_writers[0] = 0;
	num_writers[1] = 0;

	isEmpty[0] = 1;
	isEmpty[1] = 1;
	isFull[0] = 0;
	isFull[1] = 0;

	init_waitqueue_head(&read_queue[0]);
	init_waitqueue_head(&read_queue[1]);
	init_waitqueue_head(&write_queue[0]);
	init_waitqueue_head(&write_queue[1]);

	sema_init(&sema_count[0], 1);
	sema_init(&sema_count[1], 1);

	sema_init(&sema_read[0], 1);
	sema_init(&sema_read[1], 1);

	sema_init(&sema_write[0], 1);
	sema_init(&sema_write[1], 1);

	return 0;
}

void cleanup_module() {
	unregister_chrdev(my_major, MY_MODULE);
	return;
}

int my_open(struct inode *inode, struct file *filp) {
	PrivateData *tmpData;
	if ((inode == NULL) || (filp == NULL)) {
		return -EFAULT;
	}

	int minor = MINOR(inode->i_rdev);
	if ((minor < 0) || (minor > 1)) {
		return -ENODEV;
	}
	down_interruptible(&sema_count[minor]);
	if ((filp->f_mode) & FMODE_READ) {
		num_readers[minor]++;
	}
	if ((filp->f_mode) & FMODE_WRITE) {
		num_writers[minor]++;
	}
	up(&sema_count[minor]);
	//save the minor and private iKey at private_data
	tmpData = (PrivateData*) kmalloc(sizeof(PrivateData), GFP_KERNEL);
	if (!tmpData) {
		return -ENOMEM;
	}
	tmpData->minor = minor;
	tmpData->PIKey = iKey;

	filp->private_data = (void *) tmpData;


	return 0;
}

int my_release(struct inode *inode, struct file *filp) {
	int minor = -1;
	if (!inode || !filp) {
		return -ENOENT;
	}
	minor = MINOR(inode->i_rdev);
	if ((minor < 0) || (minor > 1)) {
		return -EINVAL;
	}
	down_interruptible(&sema_count[minor]);
	if ((filp->f_mode) & FMODE_READ) {
		num_readers[minor]--;
		wake_up_interruptible(&write_queue[minor]);
	}
	if ((filp->f_mode) & FMODE_WRITE) {
		num_writers[minor]--;
		wake_up_interruptible(&read_queue[minor]);
	}

	up(&sema_count[minor]);
	kfree(filp->private_data);
	filp->private_data = NULL;
	return 0;
}
ssize_t my_read(struct file *filp, char *buf, size_t count, loff_t *offp) {
	int minor, res, writers, writePermission;
	int size_to_read;
	minor = *((int *) (filp->private_data));
	if (!filp || count <= 0 || ((count % 8) != 0)) {
		wake_up_interruptible(&write_queue[minor]);
		return -EINVAL;
	}
	writePermission = write_permission(filp);
	down_interruptible(&sema_read[minor]);

	size_to_read = get_size_to_read(minor);
	while (size_to_read == 0) {
		writers = get_num_writers(minor);
		if (writers - writePermission == 0) {
			up(&sema_read[minor]);
			wake_up_interruptible(&write_queue[minor]);
			return EOF;
		}
		int wake_success = wait_event_interruptible(read_queue[minor],
				((isEmpty[minor] == 0) || ((get_num_writers(minor) - writePermission) == 0)));
		if (wake_success != 0) {
			up(&sema_read[minor]);
			wake_up_interruptible(&write_queue[minor]);
			return -EINTR;
		}
		size_to_read = get_size_to_read(minor);
	}
	size_to_read = size_to_read > count ? count : size_to_read;
	if (minor == 1) {
		res = handle_read_encrypt(size_to_read, minor, buf);
	} else {
		res = handle_read_decrypt(size_to_read, minor, buf,filp);
	}
	wake_up_interruptible(&write_queue[minor]);
	if (res < 0) {
		up(&sema_read[minor]);
		return -ENOMEM;
	}
	if (res > 0) {
		up(&sema_read[minor]);
		return res; //TODO is it ok? maybe size_to_read - res
	}
	up(&sema_read[minor]);

	return size_to_read;

}
ssize_t my_write(struct file *filp, const char *buf, size_t count, loff_t *offp) {
	int minor, size_to_write, res, readPermission,readers;
	minor = *((int *) (filp->private_data));
	if (!filp || count <= 0 || ((count % 8) != 0)) {
		wake_up_interruptible(&read_queue[minor]);
		return -EINVAL;
	}
	readPermission = read_permission(filp);
	down_interruptible(&sema_write[minor]);
	size_to_write = get_size_to_write(minor);
	while (size_to_write == 0) {
		readers = get_num_readers(minor);
		//printk("readers:%d \t readPermission:%d\n",readers,readPermission);
		if ((readers - readPermission) == 0) {
			up(&sema_write[minor]);
			wake_up_interruptible(&read_queue[minor]);
			return EOF;
		}
		int wake_success = wait_event_interruptible(write_queue[minor],
				((isFull[minor] == 0) || ((get_num_readers(minor) - readPermission) == 0)));
		if (wake_success != 0) {
			up(&sema_write[minor]);
			wake_up_interruptible(&read_queue[minor]);
			return -EINTR;
		}
		size_to_write = get_size_to_write(minor);

	}
	size_to_write = size_to_write > count ? count : size_to_write;
	if (minor == 0) {
		res = handle_write_decrypt(size_to_write, minor, buf);
	} else {
		res = handle_write_encrypt(size_to_write, minor, buf,filp);
	}
	wake_up_interruptible(&read_queue[minor]);
	if (res) { //number of bytes not copied. 0 means success copying to user
		up(&sema_write[minor]);
		return res;
	}
	up(&sema_write[minor]);
	return size_to_write;
}
loff_t my_llseek(struct file* a, loff_t b, int c) {
	return -ENOSYS;
}

int my_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
		unsigned long arg) {
	if (cmd==HW4_SET_KEY) {
		((PrivateData*)(filp->private_data))->PIKey = arg;
	} else {
		return -ENOTTY;
	}
	return 0;
}



